""" Integrate Faker to the Mixer. """
import decimal as dc
import locale as pylocale
from collections import defaultdict

from faker import Factory, Generator
from random import randint
from faker.config import DEFAULT_LOCALE, AVAILABLE_LOCALES, PROVIDERS
from faker.providers import BaseProvider

SMALLINT = 32767  # Safe in most databases according to Django docs

GENRES = ('general', 'pop', 'dance', 'traditional', 'rock', 'alternative', 'rap', 'country',
          'jazz', 'gospel', 'latin', 'reggae', 'comedy', 'historical', 'action', 'animation',
          'documentary', 'family', 'adventure', 'fantasy', 'drama', 'crime', 'horror', 'music',
          'mystery', 'romance', 'sport', 'thriller', 'war', 'western', 'fiction', 'epic',
          'tragedy', 'parody', 'pastoral', 'culture', 'art', 'dance', 'drugs', 'social')


class MixerProvider(BaseProvider):

    """ Implement some mixer methods. """

    def __init__(self, generator):
        self.providers = []
        self.generator = generator

    def load(self, providers=PROVIDERS, locale=None):
        if locale is None:
            locale = self.generator.locale

        for pname in providers:
            pcls, lang_found = Factory._get_provider_class(pname, locale)
            provider = pcls(self.generator)
            provider.__provider__ = pname
            provider.__lang__ = lang_found
            self.generator.add_provider(provider)

    def big_integer(self):
        """ Get a big integer.

        Get integer from -9223372036854775808 to 9223372036854775807.

        """
        return self.generator.random_int(-9223372036854775808, 9223372036854775807)

    def ip_generic(self, protocol=None):
        """ Get IP (v4 or v6) address.

        :param protocol:
            Set protocol to 'ipv4' or 'ipv6'. Generate either IPv4 or
            IPv6 address if none.

        """
        if protocol == 'ipv4':
            return self.generator.ipv4()

        if protocol == 'ipv6':
            return self.generator.ipv6()

        return self.generator.ipv4() if self.generator.boolean() else self.generator.ipv6()

    def small_decimal(self, left_digits=None, right_digits=None, **kwargs):
        return self.generator.pydecimal(
            left_digits=left_digits or randint(1, 10),
            right_digits=right_digits or randint(1, 10),
            **kwargs)

    def positive_decimal(self, **kwargs):
        """ Get a positive decimal. """
        return self.small_decimal(positive=True, **kwargs)

    def positive_integer(self, max=2147483647):  # noqa
        """ Get a positive integer. """
        return self.random_int(0, max=max)  # noqa

    def small_integer(self, min=-SMALLINT, max=SMALLINT):  # noqa
        """ Get a positive integer. """
        return self.random_int(min=min, max=max)  # noqa

    def small_positive_integer(self, max=SMALLINT):  # noqa
        """ Get a positive integer. """
        return self.random_int(0, max=max)  # noqa

    @staticmethod
    def uuid():
        import uuid
        return str(uuid.uuid1())

    def json(self):
        """Generate random dict with str keys and int values.
        """
        return dict((self.pystr(), self.random_int()) for _ in range(10))

    def genre(self):
        return self.random_element(GENRES)

    def percent(self):
        return self.random_int(0, 100)

    def percent_decimal(self):
        return dc.Decimal("0.%d" % self.random_int(0, 99)) + dc.Decimal('0.01')

    def title(self):
        words = self.generator.words(6)
        return " ".join(words).title()

    def coordinates(self):
        return (self.generator.latitude(), self.generator.longitude())

    def pybytes(self, size=20):
        return self.pystr(size).encode('utf-8')


class MixerGenerator(Generator):

    """ Support dynamic locales switch. """

    def __init__(self, locale=DEFAULT_LOCALE, providers=PROVIDERS, **config):
        self._locale = None
        self._envs = defaultdict(self.__create_env)
        self.locale = locale
        super(MixerGenerator, self).__init__(**config)
        self.env.load(providers)

    def __create_env(self):
        return MixerProvider(self)

    def __getattr__(self, name):
        return getattr(self.env, name)

    @property
    def providers(self):
        return self.env.providers

    @providers.setter
    def providers(self, value):
        self.env.providers = value

    @property
    def locale(self):
        return self._locale

    @locale.setter
    def locale(self, value):
        value = pylocale.normalize(value.replace('-', '_')).split('.')[0]
        if value not in AVAILABLE_LOCALES:
            value = DEFAULT_LOCALE

        if value == self._locale:
            return None

        nenv = self._envs[value]
        senv = self.env
        self._locale = value

        if senv.providers and not nenv.providers:
            nenv.load([p.__provider__ for p in senv.providers], value)

    @property
    def env(self):
        return self._envs[self._locale]

    def set_formatter(self, name, method):
        if not hasattr(self.env, name):
            setattr(self.env, name, method)


faker = MixerGenerator()
